#include "Archivo.h"
#include "funciones.h"
#include <algorithm>

namespace Toolkit
{
Archivo:: Archivo (const unsigned long int &tamanoUnidad, const string &ruta, const string &nombre,
	 const TipoSerializacion &tserializacion, const stringstream &metadata)
	 : Almacenamiento(tamanoUnidad, tserializacion)
{ 
	this-> ruta = ruta;
	this-> nombre = nombre;
	elementos = 0;
	
	tamanoMetadata = metadata.str().size();
	
	const char* filename = (ruta + nombre).c_str();
	
	stream = new fstream ();

	if (tserializacion == BINARIO) {
		crearArchivo(filename, (fstream*)stream, tserializacion);				
		stringstream preambulo(ios_base::out| ios_base::in |ios_base::binary);
		
		offsetElementos = 3 * sizeof(unsigned long int) + metadata.str().size();
			
		preambulo.write((char*)&tamanoUnidad, sizeof(unsigned long int));
		preambulo.write((char*)&offsetElementos, sizeof(unsigned long int));
		preambulo.write((char*)&elementos, sizeof(unsigned long int));
		
		stream->write(preambulo.str().c_str(), preambulo.str().size());
		stream->write(metadata.str().c_str(), tamanoMetadata);		
		
	} else if (tserializacion == TEXTO) {		
		crearArchivo(filename, (fstream*)stream, tserializacion);
					
		const char* nombrePreambulo = (ruta + nombre + ".p").c_str();
		fstream preambulo(nombrePreambulo, ios_base::out);

		preambulo << intToStr(tamanoUnidad, CARCACTERES_TAMANO) << 
			intToStr(elementos, CARCACTERES_TAMANO) <<
			intToStr(tamanoMetadata, CARCACTERES_TAMANO) << metadata.str();				
		preambulo.close();
		
		offsetElementos = 0;				
	}
		
}

Archivo::Archivo (const string& ruta, const string& nombre,
		const TipoSerializacion& tserializacion) : Almacenamiento(tserializacion)
{
	this-> ruta = ruta;
	this-> nombre = nombre;
	string filename = ruta + nombre;
	if (tserializacion == BINARIO) {
		stream = new fstream(filename.c_str(), ios_base::in | ios_base::out | ios_base::binary);
		
		stream->read((char*)&tamanoUnidad, sizeof(unsigned long int));		
		stream->read((char*)&offsetElementos, sizeof(unsigned long int));		
		stream->read((char*)&elementos, sizeof(unsigned long int));
		
		tamanoMetadata = offsetElementos - 3 * sizeof(unsigned long int);
		
	} else if (tserializacion == TEXTO) {
		stream = new fstream (filename.c_str(), ios_base::in | ios_base::out);
		
		fstream preambulo((filename + ".p").c_str(), ios_base::in);
		
		char buffer[CARCACTERES_TAMANO];		
		preambulo.read(buffer, CARCACTERES_TAMANO);
		tamanoUnidad = strToInt(string(buffer));
		preambulo.read(buffer, CARCACTERES_TAMANO);
		elementos = strToInt(string(buffer));		
		preambulo.read(buffer, CARCACTERES_TAMANO);
		tamanoMetadata = strToInt(string(buffer));
		
		preambulo.close();

		offsetElementos = 0;		
	}
	
	//se reconstruye el mapa de nulidad
	fstream nul((filename + ".nul").c_str(), ios_base::in |ios_base::binary);
	char buffer[sizeof(unsigned long int)];	
	while (!nul.eof()) {
		nul.read(buffer, sizeof(unsigned long int));		
		espacios.push_back((unsigned long int) *buffer);				
	}
	nul.close();
	//TODO aparentemente multiplica elementos, parcheo:
	espacios.unique();
	
}

void Archivo::leerMetaData(stringstream &metadata)
{
	if (tserializacion == BINARIO) {
		unsigned long int comienzoMeta = 3 * sizeof(unsigned long int);
		stream->seekg(comienzoMeta, ios_base::beg);
			
		char buffer[tamanoMetadata];
		stream->read(buffer, tamanoMetadata);
		
		metadata.str("");
		metadata.write(buffer, tamanoMetadata);
	} else if (tserializacion == TEXTO) {
		const char* nombrePreambulo = (ruta + nombre + ".p").c_str();
		fstream preambulo(nombrePreambulo, ios_base::in | ios_base::out);
		preambulo.seekg(3*CARCACTERES_TAMANO, ios_base::beg);
		
		char buffer[tamanoMetadata];
		preambulo.read(buffer, tamanoMetadata);
		
		metadata.str("");
		metadata.write(buffer, tamanoMetadata);
		
		preambulo.close();
	}
	
}

void Archivo::actualizarMetadata(stringstream &metadata)
{		
	if (tserializacion == BINARIO) {
		unsigned long int comienzoMeta = 3 * sizeof(unsigned long int);
		
		char buffer[tamanoMetadata];
		metadata.read(buffer, tamanoMetadata);
		
		stream->seekg(comienzoMeta, ios_base::beg);
		stream->write(buffer, tamanoMetadata);	
	} else if (tserializacion == TEXTO) {
		const char* nombrePreambulo = (ruta + nombre + ".p").c_str();
		fstream preambulo(nombrePreambulo, ios_base::in | ios_base::out);		
		preambulo.seekg(3*CARCACTERES_TAMANO, ios_base::beg);
		
		preambulo.write(metadata.str().c_str(), tamanoMetadata);
		
		preambulo.close();		
	}
}


Archivo::~Archivo()
{	
	//se persiste el mapa de nulidad	
	const char* filename = (ruta + nombre + ".nul").c_str();
	fstream* nul = new fstream();
	crearArchivo(filename, nul);	
	while (!espacios.empty())
	{		
		nul->write((char*)&espacios.front(), sizeof(unsigned long int));
		espacios.pop_front();		
	}
	nul->close();
	delete nul;
	
	//se actualiza el contador de elementos
	//TODO esto es potencialmente problematico (si se corta la luz y eso)
	if (tserializacion == BINARIO) {
		stream->seekg(2*sizeof(unsigned long int), ios_base::beg);
		stream->write((char*)&elementos, sizeof(unsigned long int));			
	} else if (tserializacion == TEXTO) {
		const char* nombrePreambulo = (ruta + nombre + ".p").c_str();
		fstream preambulo(nombrePreambulo, ios_base::in | ios_base::out);
		preambulo.seekg(CARCACTERES_TAMANO, ios_base::beg);
		preambulo.write(intToStr(elementos, CARCACTERES_TAMANO).c_str(), CARCACTERES_TAMANO);
		preambulo.close();
	}
	
	((fstream*)stream)->close();
	delete stream;
}

unsigned long int Archivo::guardarDato (const stringstream &serializacion)
{
	if (espacios.empty()) {
		return Almacenamiento::guardarDato(serializacion);
	} else {
		unsigned long int pos = espacios.front();
		espacios.pop_front();
		Almacenamiento::guardarDato(serializacion,pos); 
		return pos;		
	}	
}

void Archivo::recuperarDato (const unsigned long int posicion, stringstream &resultado) const
{
	if ((int)count(espacios.begin(), espacios.end(), posicion) > 0) {
		throw PosicionInvalidaException();	
	}
	Almacenamiento::recuperarDato(posicion, resultado);
}

void Archivo::eliminarDato (unsigned const long int posicion, stringstream &resultado)
{
	recuperarDato(posicion, resultado);	
	espacios.push_back(posicion);	
}

void Archivo::crearArchivo(const char* filename, fstream* archivo, const TipoSerializacion &tserializacion)
{	
	if (tserializacion == TEXTO) {	
		archivo->open(filename, ios_base::in |ios_base::out | ios_base::trunc);
	} else if (tserializacion == BINARIO) {
		archivo->open(filename, ios_base::in |ios_base::out | ios_base::binary | ios_base::trunc);
	}	
			
	if (!archivo->is_open()) {
		
		ofstream aux;
		aux.open(filename);
		if (aux.is_open()) {
			aux.close();	
		}
		
		if (tserializacion == TEXTO) {	
			archivo->open(filename, ios_base::in |ios_base::out | ios_base::trunc);
		} else if (tserializacion == BINARIO) {
			archivo->open(filename, ios_base::in |ios_base::out | ios_base::binary | ios_base::trunc);
		}		
	}
	
}


}
